package com.coin.dao.plugins;


import java.sql.SQLException;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Properties;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.ExecutorException;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.property.PropertyTokenizer;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

@Intercepts({@Signature(
        type = Executor.class,
        method = "update",
        args = {MappedStatement.class, Object.class}
), @Signature(
        type = Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
)})
public class SqlMonitorManager implements Interceptor {
    public static final String QUERY = "query";
    public static final String UPDATE = "update";
    private static final Logger LOGGER = LoggerFactory.getLogger(SqlMonitorManager.class);
    private static final String SHOW_SQL_PROP = "show_sql";
    private static final String DEFAULT_MAX_PARAMETERS_SIZE_PROP = "maxParameters";
    private static final String MONITOR_CALLBACK_PROP = "callbackClass";
    private static final int DEFAULT_MAX_PARAMETERS = 10000;
    private int maxParameters = 10000;
    private SqlEvent callback;
    private boolean showSql = false;

    public SqlMonitorManager() {
    }

    public Object intercept(Invocation invocation) throws Throwable {
        Date begin = new Date();
        Object result = null;
        int queryRows = -1;
        Throwable t = null;
        boolean var12 = false;

        Date end;
        try {
            var12 = true;
            result = invocation.proceed();
            queryRows = this.getQueryRows(invocation, result);
            var12 = false;
        } catch (Throwable var13) {
            t = var13;
            throw var13;
        } finally {
            if (var12) {
                end = new Date();
                String sql = this.monitorExcuteSql(invocation);
                if (this.callback != null) {
                    this.callback.onExecuteSql(sql, begin, System.currentTimeMillis() - end.getTime(), queryRows, t);
                }

            }
        }

        end = new Date();
        String sql = this.monitorExcuteSql(invocation);
        if (this.callback != null) {
            this.callback.onExecuteSql(sql, begin, System.currentTimeMillis() - end.getTime(), queryRows, t);
        }

        return result;
    }

    protected int getQueryRows(Invocation invocation, Object result) {
        if ("query".equals(invocation.getMethod().getName())) {
            if (result == null) {
                return 0;
            }

            if (result instanceof Collection) {
                return ((Collection) result).size();
            }
        }

        return -1;
    }

    private String monitorExcuteSql(Invocation invocation) {
        Object[] args = invocation.getArgs();
        if (args != null && args.length > 1) {
            MappedStatement ms = (MappedStatement) args[0];
            ms = ms.getConfiguration().getMappedStatement(ms.getId());
            BoundSql boundSql = ms.getBoundSql(args[1]);
            String sql = boundSql.getSql();

            try {
                sql = this.setParameters(ms, boundSql, args[1]);
            } catch (Exception var7) {
                LOGGER.debug(var7.getMessage(), var7);
            }

            sql = formatSql(sql);
            if (this.showSql) {
                LOGGER.info(sql);
                System.out.println(sql);
            }

            return sql;
        } else {
            return null;
        }
    }

    private static String formatSql(String sql) {
        sql = StringUtils.replace(sql, "\n", " ");
        sql = StringUtils.replace(sql, "\t", " ");
        return sql;
    }

    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    public void setProperties(Properties properties) {
        if (properties != null) {
            String value;
            if (properties.containsKey("show_sql")) {
                value = properties.getProperty("show_sql");
                if (Boolean.TRUE.toString().equals(value)) {
                    this.showSql = true;
                }
            }

            if (properties.containsKey("maxParameters")) {
                value = properties.getProperty("maxParameters");
                this.maxParameters = value == null ? 10000 : Integer.parseInt(value);
            }

            if (properties.containsKey("callbackClass")) {
                value = properties.getProperty("callbackClass");

                try {
                    Class c = Class.forName(value);
                    if (!SqlEvent.class.isAssignableFrom(c)) {
                        throw new RuntimeException("error to initialize property 'callbackClass', class must implements " + SqlEvent.class.getName());
                    }

                    this.callback = (SqlEvent) c.newInstance();
                } catch (Exception var4) {
                    throw new RuntimeException("error to initialize property 'callbackClass'", var4);
                }
            }

        }
    }

    public String setParameters(MappedStatement mappedStatement, BoundSql boundSql, Object parameterObject) throws SQLException {
        Configuration configuration = mappedStatement.getConfiguration();
        TypeHandlerRegistry typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        String sql = boundSql.getSql();
        if (parameterMappings != null) {
            if (parameterMappings.size() > this.maxParameters) {
                LOGGER.warn("ingore set parameters due to too much parameters for sql id[" + mappedStatement.getId() + "] max is " + this.maxParameters + " current is " + parameterMappings.size());
                return sql;
            }

            MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject);
            String[] parameters = new String[parameterMappings.size()];

            for (int i = 0; i < parameterMappings.size(); ++i) {
                ParameterMapping parameterMapping = (ParameterMapping) parameterMappings.get(i);
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    String propertyName = parameterMapping.getProperty();
                    PropertyTokenizer prop = new PropertyTokenizer(propertyName);
                    Object value;
                    if (parameterObject == null) {
                        value = null;
                    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                        value = parameterObject;
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (propertyName.startsWith("__frch_") && boundSql.hasAdditionalParameter(prop.getName())) {
                        value = boundSql.getAdditionalParameter(prop.getName());
                        if (value != null) {
                            value = configuration.newMetaObject(value).getValue(propertyName.substring(prop.getName().length()));
                        }
                    } else {
                        value = metaObject == null ? null : metaObject.getValue(propertyName);
                    }

                    TypeHandler typeHandler = parameterMapping.getTypeHandler();
                    if (typeHandler == null) {
                        throw new ExecutorException("There was no TypeHandler found for parameter " + propertyName + " of statement " + mappedStatement.getId());
                    }

                    parameters[i] = this.formatValue(value);
                }
            }

            sql = this.getSql(sql, parameters);
        }

        return sql;
    }

    private String getSql(String sql, String[] parameters) {
        int len = sql.length();
        StringBuilder builder = new StringBuilder(len * 5);
        int pos = 0;

        for (int i = 0; i < len; ++i) {
            char c = sql.charAt(i);
            if (c == 63) {
                builder.append(parameters[pos++]);
            } else {
                builder.append(c);
            }
        }

        return builder.toString();
    }

    private String formatValue(Object value) {
        return value == null ? "NULL" : (value instanceof Number ? value.toString() : "'" + value.toString() + "'");
    }

    public void setShowSql(boolean showSql){
        this.showSql=showSql;
    }
}
